/*
 * Copyright (C) 2010-2011 sunjumper@163.com
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *  http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package mf.mfrpc.client;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import mf.mfrpc.InvokeException;
import mf.mfrpc.http.HttpClient;
import mf.mfrpc.http.HttpConstants;
import mf.mfrpc.http.HttpUtil;
import mf.mfrpc.http.TransferType;
import mf.mfrpc.serialize.AdapterDataSource;
import mf.mfrpc.serialize.DataSource;
import mf.mfrpc.serialize.DataSourceIterator;
import mf.mfrpc.serialize.Parameter;
import mf.mfrpc.serialize.ParameterType;
import mf.mfrpc.serialize.Parser;
import mf.mfrpc.util.Base64;

/**
 * Http方式的RPC请求端。
 */
public class RPCHttpClient<T> extends HttpClient<T> {

    /** http请求参数的名称 */
    public static final String PARAMETER_NAME = "pName";

    /** 对象序列化解析器 */
    protected Parser parser;

    public RPCHttpClient(Parser parser) {

        this.parser = parser;
    }

    public RPCHttpClient(String url, Parser parser) {
        this.url = url;
        this.parser = parser;
    }

    /**
     * Http方式的RPC方法调用。
     *
     * @param obj 调用参数。
     * 
     * @return 返回调用结果。
     *
     * @see mf.mfrpc.http.HttpParser#parse(javax.servlet.http.HttpServletRequest) 
     */
    public T invoke(Object... obj) throws InvokeException {

        //serialized parameter object
        Parameter param = new Parameter(obj);

        //check and add DataSource or DataSourceIterator parameter
        if (param.getParameters() != null) {
            int stream = 0;
            for (int i = 0; i < param.getParameterTypes().length; i++) {
                byte type = param.getParameterTypes()[i];
                if (ParameterType.DATASOURCE == type) {
                    addDataSource((DataSource) param.getParameters()[i]);
                    stream++;
                } else if (ParameterType.DATASOURCES == type) {
                    DataSourceIterator di = (DataSourceIterator) (param.getParameters()[i]);
                    while (di.hasNext()) {
                        addDataSource(di.next());
                    }
                    stream++;
                }

                //only one DATASOURCE or DATASOURCES can be transfered
                if (stream > 1) {
                    throw new IllegalArgumentException("only one DataSource or DataSourceIterator parameter can be transfered");
                }
            }
        }
        ByteArrayOutputStream bos = new ByteArrayOutputStream(1024);
        parser.writeObject(bos, param);
        //将请求参数对象序列化以字符串的方式作为第一个参数传递, 流数据必定从第二个开始
        addParameter(PARAMETER_NAME, bos.toByteArray());
        return (T) super.invoke();

    }

    /**
     * rpc通过http请求后的响应处理，可继承并自定义此处理过程。
     * 
     * @param con http请求的联接。
     *
     * @return 可自定义的响应处理，默认支持Object、DataSource、DataSourceIterator等返回类型。
     * 
     * @throws IOException 抛出可能发生的IO异常。
     *
     * @see mf.mfrpc.serialize.DataSource
     * @see mf.mfrpc.serialize.DataSourceIterator
     */
    @Override
    protected T handleInputStream(HttpURLConnection con) throws IOException {
        TransferType tt = HttpUtil.getTransferType(con);
        log.debug("TransferType : {}", tt.toString());
        InputStream in = con.getInputStream();
        switch (tt) {
            case STREAM: {
                return (T) in;
            }
            case DATASOURCE: {
                String props = con.getHeaderField(HttpConstants.DATASOURCE_INFORMATION_HEADER);

                //adapte the DataSource
                AdapterDataSource ds = new AdapterDataSource((DataSource) parser.readObject(new ByteArrayInputStream(Base64.decode(props))));
                ds.setInputStream(in);
                return (T) ds;
            }
            case OBJECT: {
                return (T) parser.readObject(in);
            }
            default:
                break;
        }
        return null;
    }

    public Parser getParser() {
        return parser;
    }

    public void setParser(Parser parser) {
        this.parser = parser;
    }
}
